#include "renderer.hpp"
#include "engine.hpp"

namespace sdl_engine {

Renderer::Renderer(const CreateInfo& createInfo) {
    renderer_ = SDL_CreateRenderer(engine.GetWindow().Raw(),
                                   -1,
                                   createInfo.flags_);

    if (!renderer_) {
        LogeWithSDLErr("Renderer create failed");
    }
}

Renderer::~Renderer() {
    SDL_DestroyRenderer(renderer_);
}

void Renderer::SetDrawColor(const Color& color) {
    if (SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, color.a) < 0) {
        Loge(SDL_GetError());
    }
}

void Renderer::Clear() {
    if (SDL_RenderClear(renderer_) < 0) {
        Loge(SDL_GetError());
    }
}

void Renderer::SetTarget(Texture* texture) {
    if (texture && texture->Valid()) {
        if (SDL_SetRenderTarget(renderer_, texture->Raw()) < 0) {
            Loge(SDL_GetError());
        }
    } else {
        if (SDL_SetRenderTarget(renderer_, nullptr) < 0) {
            Loge(SDL_GetError());
        }
    }
}

void Renderer::SetTarget2Default() {
    if (engine.IsCanvaResizable()) {
        SetTarget(engine.canva_.get());
    } else {
        SetTarget(nullptr);
    }
}

void Renderer::SetViewport(int x, int y, int w, int h) {
    SDL_Rect rect = {x, y, w, h};
    if (SDL_RenderSetViewport(renderer_, &rect) < 0) {
        Loge(SDL_GetError());
    }
}

Rect Renderer::GetViewport() const {
    SDL_Rect r;
    SDL_RenderGetViewport(renderer_, &r);
    return rect(r.x, r.y, r.w, r.h);
}

void Renderer::RenderPresent() {
    SDL_RenderPresent(renderer_);
}

void Renderer::RenderPoint(const Point& p) {
    if (SDL_RenderDrawPoint(renderer_, p.x, p.y) < 0) {
        Loge(SDL_GetError());
    }
}

void Renderer::RenderLine(const Point& p1, const Point& p2) {
    if (SDL_RenderDrawLine(renderer_, p1.x, p1.y, p2.x, p2.y) < 0) {
        Loge(SDL_GetError());
    }
}

void Renderer::RenderFillRect(const Rect& rect) {
    SDL_Rect sdlRect = Rect2SDL(rect);
    if (SDL_RenderFillRect(renderer_, &sdlRect) < 0) {
        Loge(SDL_GetError());
    }
}

void Renderer::RenderRect(const Rect& rect) {
    SDL_Rect sdlRect = Rect2SDL(rect);
    if (SDL_RenderDrawRect(renderer_, &sdlRect) < 0) {
        Loge(SDL_GetError());
    }
}

void Renderer::RenderUTF8(Font& font,
                          const std::string& text,
                          const Point& position,
                          const Color& color) {
    if (!font.Raw())
        return;
    auto surface = TTF_RenderUTF8_Blended(font.Raw(), text.c_str(), Color2SDL(color));
    if (!surface) {
        Loge(TTF_GetError());
    }
    Texture texture(surface);
    RenderCopy(&texture, position);
    SDL_FreeSurface(surface);
}

void Renderer::RenderCopy(Texture* texture,
                          const Point& position,
                          const Size& size,
                          const std::optional<Color>& color) {
    if (!texture || !texture->Valid())
        return;
    SDL_Rect dstRect = {position.x, position.y, size.w, size.h};
    if (size == Size{0, 0}) {
        dstRect.w = texture->GetSize().w;
        dstRect.h = texture->GetSize().h;
    }

    if (color)
        setTextureColor(*texture, color.value());
    if (SDL_RenderCopy(renderer_, texture->Raw(), nullptr, &dstRect) < 0) {
        Logw(SDL_GetError());
    }
    if (color)
        setTextureColor(*texture, Color::White);
}

void Renderer::RenderCopyTo(Texture* src, Texture* dst,
                            const Point& position,
                            const Size& size,
                            const std::optional<Color>& color) {
    SetTarget(dst);
    RenderCopy(src, position, size, color);
    SetTarget2Default();
}

void Renderer::RenderCopy(SubTexture* texture,
                          const Point& position,
                          const Size& size,
                          const std::optional<Color>& color) {
    if (!texture && !texture->GetTexture() && !texture->GetTexture()->Valid())
        return;
    SDL_Rect srcRect = Rect2SDL(texture->rect_);

    SDL_Rect dstRect = {position.x, position.y, size.w, size.h};
    if (size == Size{0, 0}) {
        dstRect.w = texture->GetSize().w;
        dstRect.h = texture->GetSize().h;
    }

    if (color)
        setTextureColor(*texture->GetTexture(), color.value());
    if (SDL_RenderCopy(renderer_, texture->GetTexture()->Raw(), &srcRect, &dstRect) < 0) {
        Logw(SDL_GetError());
    }
    if (color)
        setTextureColor(*texture->GetTexture(), Color::White);
}

void Renderer::RenderCopyTo(SubTexture* src, Texture* dst,
                            const Point& position,
                            const Size& size,
                            const std::optional<Color>& color) {
    SetTarget(dst);
    RenderCopy(src, position, size, color);
    SetTarget2Default();
}

void Renderer::setTextureColor(Texture& texture, const Color& color) {
    if (texture.Valid()) {
        SDL_SetTextureColorMod(texture.Raw(),
                               color.r,
                               color.g,
                               color.b);
        SDL_SetTextureAlphaMod(texture.Raw(), color.a);
    }
}

}
